import Layers from "./layers";

/**
 * 模型元素
 */
interface ModelItem {
    /**
     * 需要操作的 dom 元素
     */
    ele: HTMLElement;
    /**
     * 需要绑定到 dom 元素的字段名
     */
    name: string;
    /**
     * 需要绑定到 dom 元素的什么属性上
     */
    attr: string;
}

/**
 * 数据格式化函数
 *
 * 原始数据需要经过格式化，之后再绑定到 dom 元素上
 */
type ParserCallback = (data: any) => any;


/**
 * 模版引擎
 *
 * 简单的封装，支持简单的数据绑定，
 *
 * 对于需要大量频繁更新 dom 组件内容的场景，这个组件会非常实用。
 *
 * Vue 环境下，实际也可以手动实例化 vue 组件实现
 */
class Template {
    /**
     * dom 元素 ID
     */
    id: number;
    /**
     * 模型列表
     */
    model: ModelItem[];
    /**
     * 顶级 dom 节点
     * @private
     */
    private _node: HTMLElement | undefined;
    /**
     * 需要绑定到 dom 对象的原始数据
     * @private
     */
    private _bind: Record<string, any>;
    /**
     * 绑定前需要进行一些必要的格式化操作
     * @private
     */
    private _parser: ParserCallback;

    constructor() {
        // 如果有必要，可以给 dom 设置一个 ID
        this.id = new Date().getTime();

        // 使用了表达式的节点列表
        this.model = [];

        // 顶级容器
        this._node = undefined;

        // 当前绑定的数据
        this._bind = {};

        // 数据渲染前的格式化处理
        this._parser = data => data;
    }

    /**
     * 渲染界面前的数据格式化操作
     *
     * @param call {ParserCallback} 函数回调
     */
    set parser(call: ParserCallback) {
        this._parser = call;
    }

    /**
     * 通过 html 直接构建一个 dom 对象
     *
     * 所有内容会被装到一个 div 中，注意这会对布局产生影响
     *
     * @param txt {string}
     * @returns {HTMLElement}
     */
    html(txt: string): HTMLElement {
        const dom = document.createElement('div');
        dom.innerHTML = txt;

        this.node = dom;
        return dom;
    }

    /**
     * 返回 dom 组件
     * @returns {HTMLElement | undefined}
     */
    get node(): HTMLElement | undefined {
        return this._node;
    }

    /**
     * 设置 dom 组件
     * @param value {HTMLElement}
     */
    set node(value: HTMLElement) {
        // 记录所有绑定的节点
        value.querySelectorAll('*').forEach((item) => {
            const ret = item.getAttribute('ref');
            if (Layers.isNotBlank(ret)) {
                // e.g.: name.text 指将 name 绑定到 text 属性上
                const cfg = ret.split('.');
                this.model.push({ele: item, name: cfg[0], attr: cfg[1]});
            }
        });
        this._node = value;
    }

    /**
     * 获取绑定数据
     * @returns {Record<string, any>}
     */
    get bind(): Record<string, any> {
        return this._bind;
    }

    /**
     * 刷新数据
     *
     * @param data 数据
     */
    set bind(data: Record<string, any>) {
        // 记录绑定的数据
        this._bind = data;

        // 格式化数据
        const obj = this._parser(data);

        // 数据绑定到 dom 对象上
        this.model.forEach((item) => {
            const value = obj[item.name];
            if ('text' === item.attr) {
                item.ele.innerHTML = value;
            } else if ('src' === item.attr) {
                item.ele.src = value;
            } else {
                console.error('unknown tag: ', item);
            }
        });
    }
}

export default Template;

